﻿namespace Microsoft.DevDiv.Wpf.Samples.LocalAdornerSample
{
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using Microsoft.DevDiv.Wpf.Samples;
    using System.Windows.Media;
    using System.Windows;
    using System.Windows.Input;

    class RotateAdorner : LocalAdorner
    {
        /// <summary>
        /// Helper class to manage mouse capture state in a manner that is 
        /// friendly and composable with other IInputElements that may already have capture
        /// </summary>
        /// <usage>
        /// protected override void OnMouseLeftButtonDown(System.Windows.Input.MouseButtonEventArgs e)
        ///{
        ///    if(SharedMouseCapture.TryShareCapture(this, CaptureMode.SubTree, out this.mouseCapture))
        ///    {
        ///        StartDragOperation();
        ///    }
        ///}
        ///
        ///protected override void OnMouseMove(System.Windows.Input.MouseEventArgs e)
        ///{
        ///    if(this.mouseCapture.IsMouseCaptured)
        ///    {
        ///        UpdateDragOperation()
        ///    }
        ///}
        ///
        ///protected override void OnMouseLeftButtonUp(System.Windows.Input.MouseButtonEventArgs e)
        ///{
        ///    this.mouseCapture.Release();
        ///    CompleteDragOperation();
        ///}
        ///
        ///protected override void OnKeyDown(KeyEventArgs e)
        ///{
        ///    switch(e.Key)
        ///    {
        ///        case Key.Escape:
        ///        {
        ///            this.mouseCapture.Release();
        ///            CancelDragOperation();
        ///            e.Handled = true;
        ///            break;
        ///        }
        ///
        ///        default:
        ///        {
        ///            base.OnKeyDown(e);
        ///            break;
        ///        }
        ///    }
        ///}
        /// </usage>
        private struct SharedMouseCapture : IDisposable
        {
            public static bool TryShareCapture(IInputElement elementToCapture, CaptureMode mode, out SharedMouseCapture capture)
            {
                if(elementToCapture == null)
                {
                    throw new ArgumentNullException("elementToCapture");
                }

                bool result = false;
                if(elementToCapture.IsMouseCaptured)
                {
                    capture = new SharedMouseCapture(elementToCapture, true);
                    result = true;
                }
                else if(Mouse.Capture(elementToCapture, mode))
                {
                    capture = new SharedMouseCapture(elementToCapture, false);
                    elementToCapture.LostMouseCapture += capture.OnLostMouseCapture;
                    result = true;
                }
                else 
                {
                    capture = default(SharedMouseCapture);
                }

                return result;
            }

            private SharedMouseCapture(IInputElement elementToCapture, bool ownsCapture)
            {
                this.capturedElement = elementToCapture;
                this.ownsMouseCapture = ownsCapture;
            }

            public bool IsMouseCaptured 
            {
                get { 
                    if(this.capturedElement == null)
                    {
                        return false;
                    }
                    else 
                    {
                        return this.capturedElement.IsMouseCaptured; 
                    }
                }
            }

            public void Release()
            {
                if(this.capturedElement != null)
                {
                    OnLostMouseCapture(this.capturedElement, EventArgs.Empty);

                    if(this.ownsMouseCapture)
                    {
                        this.capturedElement.ReleaseMouseCapture();
                    }
                }
            }

            void IDisposable.Dispose() 
            {
                this.Release();
            }

            private void OnLostMouseCapture(object sender, EventArgs e)
            {
                if (this.capturedElement != null)
                {
                    this.capturedElement.LostMouseCapture -= OnLostMouseCapture;
                }

                this.capturedElement = null;
                this.ownsMouseCapture = false;
            }

            private bool ownsMouseCapture;
            private IInputElement capturedElement;
        }

        protected override void OnAdornedElementUpdated(EventArgs e)
        {
            base.OnAdornedElementUpdated(e);

            UIElement adornment = this.Adornment;
            if (adornment != null)
            {
                Size adornmentSize = adornment.RenderSize;
                double xOffset = Math.Max(0, this.RenderSize.Width - adornmentSize.Width);
                base.SetAdornmentHostOffset(new Vector(xOffset, 0));
            }
        }

        protected override void OnMouseLeftButtonDown(System.Windows.Input.MouseButtonEventArgs e)
        {
            base.OnMouseLeftButtonDown(e);

            UIElement visualParent = GetNearestIInputElementParentInVisualTree(this);
            Visual originalSource = e.OriginalSource as Visual;
            if (originalSource != null && visualParent != null)
            {
                this.isRotating = SharedMouseCapture.TryShareCapture(this, CaptureMode.SubTree, out this.mouseCapture);
            }

            if (this.isRotating)
            {
                this.rotationAnchor = visualParent;
                this.originalMouseDown = e.GetPosition(this.rotationAnchor);
                this.originalTransform = this.ReadLocalValue(UIElement.RenderTransformProperty);
                this.rotatingTransform = new RotateTransform();
                this.hostingTransform = new TransformGroup();

                Transform temp = this.originalTransform as Transform;
                if (temp != null)
                {
                    this.hostingTransform.Children.Add((Transform)temp.GetCurrentValueAsFrozen());
                }

                this.hostingTransform.Children.Add(this.rotatingTransform);

                this.RenderTransform = this.hostingTransform;
            }

            e.Handled = this.isRotating;
        }

        protected override void OnMouseMove(System.Windows.Input.MouseEventArgs e)
        {
            base.OnMouseMove(e);

            if (this.isRotating && this.rotatingTransform != null)
            {
                Point anchorOrigin = this.TransformToAncestor(this.rotationAnchor).Transform(new Point(0.0, 0.0));

                Vector originalMouseOffset = this.originalMouseDown - anchorOrigin;
                Vector currentMouseOffset = e.GetPosition(this.rotationAnchor) - anchorOrigin;

                double originalAngleRadians = Math.Atan2(originalMouseOffset.Y, originalMouseOffset.X);
                double currentAngleRadians = Math.Atan2(currentMouseOffset.Y, currentMouseOffset.X);

                this.rotatingTransform.Angle = FromRadiansToDegrees(currentAngleRadians - originalAngleRadians);
            }
        }

        protected override void OnMouseLeftButtonUp(System.Windows.Input.MouseButtonEventArgs e)
        {
            CommitRotation();
            this.mouseCapture.Release();

            base.OnMouseLeftButtonUp(e);
        }

        protected override void OnKeyDown(KeyEventArgs e)
        {
            switch(e.Key)
            {
                case Key.Escape:
                {
                    CancelRotation();
                    this.mouseCapture.Release();

                    e.Handled = true;
                    break;
                }

                default:
                {
                    base.OnKeyDown(e);
                    break;
                }
            }
        }

        protected override void OnLostMouseCapture(MouseEventArgs e)
        {
            CancelRotation();
            base.OnLostMouseCapture(e);
        }

        private void CommitRotation()
        {
            if (this.isRotating)
            {
                this.isRotating = false;
                Transform result = new MatrixTransform(this.hostingTransform.Value);
                result.Freeze();
                this.SetValue(UIElement.RenderTransformProperty, result);

                if (this.hostingTransform != null)
                {
                    this.hostingTransform.Children.Clear();
                    this.hostingTransform = null;
                }
                this.rotatingTransform = null;
                this.originalTransform = DependencyProperty.UnsetValue;
                this.rotationAnchor = null;
            }
        }

        private void CancelRotation()
        {
            if (this.isRotating)
            {
                this.isRotating = false;
                if (this.hostingTransform != null)
                {
                    this.hostingTransform.Children.Clear();
                    this.hostingTransform = null;
                }
                this.rotatingTransform = null;
                this.SetValue(UIElement.RenderTransformProperty, this.originalTransform);
                this.originalTransform = DependencyProperty.UnsetValue;
                this.rotationAnchor = null;
            }
        }

        private static UIElement GetNearestIInputElementParentInVisualTree(Visual visual)
        {
            while (visual != null)
            {
                DependencyObject parent = VisualTreeHelper.GetParent(visual);
                UIElement result = parent as UIElement;
                if (result != null)
                {
                    return result;
                }

                visual = parent as Visual;
            }

            return null;
        }

        private static double FromRadiansToDegrees(double radians)
        {
            return radians * 180.0 / Math.PI;
        }

        private SharedMouseCapture mouseCapture;

        private Point originalMouseDown;
        private object originalTransform;

        private bool isRotating;
        private RotateTransform rotatingTransform;
        private TransformGroup hostingTransform;
        private UIElement rotationAnchor;
    }
}